﻿//--------------------------------------
// Copyright     : W.A.I.S. TECHNOLOGY 2003-2008 ; http://www.wais-fr.com ; email: infos@wais-fr.com
// Author        : Henri d'Orgeval
// Contributors  : 
// Last release  : 2007/11/13 (yy/mm/dd)
// Version       : 1.0.0.2
// Pattern       : "Real Time Trace"
// Internal name : RealtimeTraceFactory.vb (RealTimeTrace Factory that handles the user interface layer)
// Description   : this class contains all Factories needed to implement RealTimeTrace within end-user application.
//                 
// Framework     : v2.0.50727 and above
//
// Licence       : Microsoft Permisse Licence
// W.A.I.S provides this code in the hope that it will be useful, but WITHOUT ANY WARRANTY , either express or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.          
// This library is free software; you can redistribute it and/or modify it under the terms of the Microsoft Permissive Licence
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//---------------------------------------


using System;
using System.Collections;
using System.Collections.Generic;
//using System.Linq;
using System.Text;

namespace RealtimeTrace
{
    /// <summary>
    /// This class is a factory used to implement RealTimeTrace within the end-user application.
    /// </summary>
    public sealed class TraceFactory 
    {
        /// <summary>
        /// Makes the default constructor private.
        /// So that this Factory cannot be instanciated.
        /// </summary>
        private TraceFactory()
        {
        }
       
        static private DataProviderType _RealTimeTraceProvider = DataProviderType.LogFile;
        /// <summary>
        /// Gets or Sets the Data Store provider for the RealTimeTrace.
        /// By default this property returns 'LogFile'
        /// </summary>
        static public DataProviderType RealTimeTraceProvider
        {
            get
            {
                return _RealTimeTraceProvider;
            }
            set
            {
                _RealTimeTraceProvider = value;
            }
        }

        static private DumpType _PreferredDumpType = DumpType.XML;
        /// <summary>
        /// Gets or Sets the type of dump to apply to the object in which an exception has occured.
        /// </summary>
        static public DumpType PreferredDumpType
        {
            get
            {
                return _PreferredDumpType;
            }
            set
            {
               _PreferredDumpType = value;
            }
        }

        /// <summary>
        /// Internal collection to store any objects that is currently dumped.
        /// The goal of this collection is to prevent infinite loop 
        /// due to a call to the RealtimeTrace framework while dumping an object.
        /// </summary>
        static private List<object> _DumpingObjects = new List<object>();

        /// <summary>
        /// Add an object to the internal collection _DumpingObjects
        /// </summary>
        /// <param name="obj">The object to be inserted in the collection</param>
        /// <returns>Returns true if execution of the method has succeeded.
        /// Returns false otherwise.</returns>
        static private bool AddDumpingObject(System.Object obj)
        {
            try
            {
                if (obj == null)
                {
                    return true;
                }

                // Check if input is a Value type
                if (obj is System.ValueType)
                {
                    return true;
                }

                // Check if input is a string
                if (obj is System.String)
                {
                    return true;
                }

                if (_DumpingObjects == null)
                {
                    _DumpingObjects = new List<object>();
                }

                if (_DumpingObjects.Contains(obj))
                {
                    return true;
                }

                _DumpingObjects.Add(obj);

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Remove an object from the internal collection _DumpingObjects
        /// </summary>
        /// <param name="obj">The object to be removed from the collection</param>
        /// <returns>Returns true if execution of the method has succeeded.
        /// Returns false otherwise.</returns>
        static private bool RemoveDumpingObject(System.Object obj)
        {
            try
            {
                if (obj == null)
                {
                    return true;
                }

                // Check if input is a Value type
                if (obj is System.ValueType)
                {
                    return true;
                }

                // Check if input is a string
                if (obj is System.String)
                {
                    return true;
                }

                if (_DumpingObjects == null)
                {
                    return true;
                }

                if (_DumpingObjects.Count == 0)
                {
                    return true;
                }

                _DumpingObjects.Remove(obj);

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        static private bool IsDumping(System.Object obj)
        {
            try
            {
                if (obj == null)
                {
                    return false;
                }

                // Check if input is a Value type
                if (obj is System.ValueType)
                {
                    return false;
                }

                // Check if input is a string
                if (obj is System.String)
                {
                    return false;
                }

                if (_DumpingObjects == null)
                {
                    return false;
                }

                if (_DumpingObjects.Count == 0)
                {
                    return false;
                }

                if (_DumpingObjects.Contains(obj))
                {
                    return true;
                }

                return false;
                
            }
            catch (Exception)
            {
                //throw;
                return false;
            }
        }

        /// <summary>
        /// Makes a dump of the object in which an exception has occured.
        /// </summary>
        /// <param name="sender">Any valid instance in which an exception has occured.</param>
        /// <returns>A string that represents a dump of the object.</returns>
        static public string ToDump(System.Object sender)
        {
            try
            {
                // Check input parameter
                if (sender == null)
                {
                    return "null";
                }

                // Check if input is a Value type
                if (sender is System.ValueType)
                {
                    return sender.ToString();
                }

                // Check if input is a string
                if (sender is System.String)
                {
                    return sender.ToString();
                }

                // Check if the sender object is already dumping
                if (IsDumping(sender))
                {
                    return "Aborted to prevent infinite loop";
                }

                // Insert the sender object into the internal collection _DumpingObjects
                // to prevent infinite loop if the dump algorithm makes the sender object call to the RealtimeTrace framework.
                if (!AddDumpingObject(sender))
                {
                    // TODO : think of it for the next release
                }

                // Check if the input implements the Itraceable interface
                if (sender is RealtimeTrace.Itraceable)
                {
                    RealtimeTrace.Itraceable myObj = (RealtimeTrace.Itraceable)sender;
                    return ToDump(myObj);
                }

                return ToDumpByReflection(sender);
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
            finally
            {
                // Remove the sender object from the internal collection _DumpingObjects
                if (!RemoveDumpingObject(sender))
                {
                    // TODO : think of it for the next release
                }
            }
        }

        /// <summary>
        /// Makes a dump of the object in which an exception has occured
        /// </summary>
        /// <param name="sender">Any valid instance in which an exception has occured.</param>
        /// <returns>A string that represents a dump of the object.</returns>
        static private string ToDump(Itraceable sender)
        {
            try
            {
                // Check input parameter
                if (sender == null)
                {
                    return "null";
                }

                // Check if input is a Value type
                if (sender is System.ValueType)
                {
                    return sender.ToString();
                }

                if (sender.PreferredDumpType == DumpType.XML)
                {
                    return ToDumpByXmlSerialization(sender);
                }

                // Try to dump the object with Reflection
                if (sender.PreferredDumpType == DumpType.Reflection)
                {
                    //TODO : implement Reflection to dump the object.
                    return "DumpType.Reflection not yet implemented";
                }

                // Try to dump the object with binary serialization
                if (sender.PreferredDumpType == DumpType.Binary)
                {
                    //TODO : implement binary serialization to dump the object.
                    return "DumpType.Binary not yet implemented";
                }


                return "not yet implemented";

            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

        /// <summary>
        /// Makes a dump of the object in which an exception has occured.
        /// Dump is done through XML Serialization.
        /// </summary>
        /// <param name="sender">Any valid instance in which an exception has occured.</param>
        /// <returns>An XML string that represents a dump of the object.</returns>
        static private string ToDumpByXmlSerialization(System.Object sender)
        {
            try
            {
                // Check input parameter
                if (sender == null)
                {
                    return "null";
                }

                // Check if the object implements the Iserializable interface
                if (!(sender is System.Xml.Serialization.IXmlSerializable))
                {
                    return "Cannot dump object because it does not implements the IXmlSerializable interface";
                }

                // try to serialize the 'value' object
                System.Xml.Serialization.XmlSerializer mySerializer = new System.Xml.Serialization.XmlSerializer(sender.GetType());
                System.Text.StringBuilder myStringBuilder = new StringBuilder();
                System.IO.StringWriter myWriter = new System.IO.StringWriter(myStringBuilder);
                try
                {
                    mySerializer.Serialize(myWriter, sender);
                }
                catch (Exception ex1)
                {
                    // this object is not serializable
                    return ex1.Message;
                }
                finally
                {
                    if (!(myWriter == null))
                    {
                        myWriter.Close();
                        myWriter.Dispose();
                        myWriter = null;
                    }
                }

                return myStringBuilder.ToString();

            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }



        static private string ToDumpByReflection(System.Object sender)
        {
            try
            {
                // Check input parameter
                if (sender == null)
                {
                    return "null";
                }

                System.Text.StringBuilder myStringBuilder = new StringBuilder();

                System.Type senderType = sender.GetType();

                foreach (System.Reflection.FieldInfo item in senderType.GetFields(System.Reflection.BindingFlags.Instance |System.Reflection.BindingFlags.NonPublic))
	            {
                    string myString = System.String.Empty;
                    try
                    {
                        myString = System.String.Format("{0}:='{1}'; ", item.Name, item.GetValue(sender));
                        
                    }
                    catch (Exception ex)
                    {
                        myString = System.String.Format("{0}:='{1}'; ", item.Name, ex.Message);
                    }
                    myStringBuilder.Append(myString);
	            }

                foreach (System.Reflection.PropertyInfo item in senderType.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public))
                {
                    string myString = System.String.Empty;
                    try
                    {
                        myString = System.String.Format("{0}:='{1}'; ", item.Name, item.GetValue(sender,System.Reflection.BindingFlags.GetProperty,null,null,null));

                    }
                    catch (Exception ex)
                    {
                        myString = System.String.Format("{0}:='{1}'; ", item.Name, ex.Message);
                    }
                    myStringBuilder.Append(myString);
                }

                return myStringBuilder.ToString();

            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }


        /// <summary>
        /// Creates all infos on the error context and stores it in the Data Storage provider.
        /// </summary>
        /// <param name="sender">The object that calls this method. 
        /// This is the object in which an exception has been raised.</param>
        /// <param name="ex0">Exception object that has been fired in the sender object.</param>
        static public void InsertTrace(System.Object sender, System.Exception ex0)
        {
            try
            {
                // Check input parameter
                if (sender == null)
                {
                    return;
                }

                // Check input parameter
                if (ex0 == null)
                {
                    return;
                }

                // try to add the error details inside the sender
                if (! AddError(sender,ex0))
                {
                    // TODO : think of it for the next release
                }

                // Build a ItraceInfo object
                ITraceInfo myTraceInfo = TraceInfoFactory.CreateTraceInfo(ex0);

                if (myTraceInfo == null)
                {
                    return;
                }

                myTraceInfo.ClassDump = ToDump(sender);

                // Check if the ITraceInfo has errors
                if (myTraceInfo is Itraceable)
                {
                    Itraceable myTraceableObject = (Itraceable)myTraceInfo;
                    if (myTraceableObject.HasErrors)
                    {
                        // we bubble the errors up to the sender object
                        if (!AddError(sender, myTraceableObject.LastError))
                        {
                            // TODO : think of it for the next release
                        }
                    }
                } // end Check if the ITraceInfo has errors

                // Insert this error into the Data store
                // first get the type of provider
                RealtimeTrace.DataProviderType myProviderType = _RealTimeTraceProvider;
                if (sender is Itraceable)
                {
                    myProviderType = ((Itraceable)sender).RealTimeTraceProvider;
                }
                ITraceDataProvider myProvider = TraceDataProviderFactory.CreateDataProvider(myProviderType);
                if (myProvider == null)
                {
                    return;
                }
                // Now lets the Data provider insert infos into the Data Source.
                if (myProvider.Insert(myTraceInfo))
                {
                    return;
                }

                // Check if an error has occured in the provider
                if (myProvider is Itraceable)
                {
                    Itraceable myTraceableProvider = (Itraceable)myProvider;
                    if (! myTraceableProvider.HasErrors)
                    {
                        return;
                    }
                    // Ok we are sure there are errors in the provider
                    // we bubble the errors up to the sender object
                    if (!AddError(sender, myTraceableProvider.LastError))
                    {
                        // TODO : think of it for the next release
                    }
                } // if (myProvider is Itraceable)



            }
            catch (Exception ex)
            {
                // try to Bubble this internal error up to the sender object.
                try
                {
                    // Bubble the errors up to the sender object
                    if (!AddError(sender, ex.Message))
                    {
                        // TODO : think of it for the next release
                    }
                }
                catch (Exception)
                {
                    
                    //throw;
                }
            } // catch(Exception ex)
        }

        /// <summary>
        /// Creates all infos on the error context and stores it in the Data Storage provider.
        /// </summary>
        /// <param name="sender">The object that calls this method. 
        /// This is the object in which an exception has been raised.</param>
        /// <param name="ex0">Exception object that has been fired in the sender object.</param>
        /// <param name="args">List of values for each parameter of the method in which the exception has been raised.</param>
        static public void InsertTrace(System.Object sender, System.Exception ex0, params object[] args)
        {
            try
            {
                // Check input parameter
                if (sender == null)
                {
                    return;
                }

                // Check input parameter
                if (ex0 == null)
                {
                    return;
                }

                // try to add the error details inside the sender
                if (!AddError(sender, ex0))
                {
                    // TODO : think of it for the next release
                }

                // Build a ItraceInfo object
                ITraceInfo myTraceInfo = TraceInfoFactory.CreateTraceInfo(ex0,args);

                if (myTraceInfo == null)
                {
                    return;
                }

                myTraceInfo.ClassDump = ToDump(sender);

                // Check if the ITraceInfo has errors
                if (myTraceInfo is Itraceable)
                {
                    Itraceable myTraceableObject = (Itraceable)myTraceInfo;
                    if (myTraceableObject.HasErrors)
                    {
                        // we bubble the errors up to the sender object
                        if (!AddError(sender, myTraceableObject.LastError))
                        {
                            // TODO : think of it for the next release
                        }
                    }
                } // end Check if the ITraceInfo has errors

                // Insert this error into the Data store
                // first get the type of provider
                RealtimeTrace.DataProviderType myProviderType = _RealTimeTraceProvider;
                if (sender is Itraceable)
                {
                    myProviderType = ((Itraceable)sender).RealTimeTraceProvider;
                }
                ITraceDataProvider myProvider = TraceDataProviderFactory.CreateDataProvider(myProviderType);
                if (myProvider == null)
                {
                    return;
                }
                // Now lets the Data provider insert infos into the Data Source.
                if (myProvider.Insert(myTraceInfo))
                {
                    return;
                }

                // Check if an error has occured in the provider
                if (myProvider is Itraceable)
                {
                    Itraceable myTraceableProvider = (Itraceable)myProvider;
                    if (!myTraceableProvider.HasErrors)
                    {
                        return;
                    }
                    // Ok we are sure there are errors in the provider
                    // we bubble the errors up to the sender object
                    if (!AddError(sender, myTraceableProvider.LastError))
                    {
                        // TODO : think of it for the next release
                    }
                } // if (myProvider is Itraceable)



            }
            catch (Exception ex)
            {
                // try to Bubble this internal error up to the sender object.
                try
                {
                    // Bubble the errors up to the sender object
                    if (!AddError(sender, ex.Message))
                    {
                        // TODO : think of it for the next release
                    }
                }
                catch (Exception)
                {

                    //throw;
                }
            }
        }

       /// <summary>
       /// Updates the properties available on the Itraceable interface.
       /// </summary>
        /// <param name="sender">The object that calls this method. 
        /// This is the object in which an exception has been raised.</param>
        /// <param name="ex0">Exception object that has been fired in the sender object.</param>
        /// <returns>Returns true if execution has succeeded.</returns>
        static private bool AddError(System.Object sender,System.Exception ex0)
        {
            try
            {
                // Check input parameter
                if (sender == null)
                {
                    return true;
                }

                // Check input parameter
                if (ex0 == null)
                {
                    return true;
                }

                // Check if sender implements the Itraceable interface
                if (! (sender is Itraceable))
                {
                    return true;
                }

                // We are sure that sender implements the Itraceable interface
                Itraceable traceableSender = (Itraceable)sender;

                // Flag that an error has occured
                traceableSender.HasErrors = true;

                // Update the LastError message
                traceableSender.LastError = ex0.Message;

                // Check if the method in which the exception has occured is declared as public
                if (ex0.TargetSite.IsPublic)
                {
                    // Clear any previous error messages to avoid a memory leak.
                    traceableSender.Errors = System.String.Empty;
                }

                // Check if the method in which the exception has occured is declared as private
                // and is an event handler.
                // TODO : Check if the method is an event handler.

                // Add this current error to the Errors property
                string myErrors = traceableSender.Errors;

                // Update the Errors message
                myErrors += ex0.Message;
                myErrors += "\r\n";

                // check for the constraint on this._Errors string length.
                // the purpose of this check is to avoid a possible memory leak.
                if (myErrors.Length > _ErrorsMaxSize)
                {
                    myErrors = ex0.Message;
                }

                traceableSender.Errors = myErrors;

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Updates the properties available on the Itraceable interface.
        /// </summary>
        /// <param name="sender">The object that calls this method. 
        /// This is the object in which an exception has been raised.</param>
        /// <param name="errorMessage">Error message that has been fired in the sender object.</param>
        /// <returns>Returns true if execution has succeeded.</param>
        /// <returns></returns>
        static private bool AddError(System.Object sender, string errorMessage )
        {
            try
            {
                // Check input parameter
                if (System.String.IsNullOrEmpty(errorMessage))
                {
                    return true;
                }

                // Check input parameter
                if (sender == null)
                {
                    return true;
                }

                // Check if sender implements the Itraceable interface
                if (!(sender is Itraceable))
                {
                    return true;
                }

                // We are sure that sender implements the Itraceable interface
                Itraceable traceableSender = (Itraceable)sender;

                // Flag that an error has occured
                traceableSender.HasErrors = true;

                // Update the LastError message
                traceableSender.LastError = errorMessage;

                // Add this current error to the Errors property
                string myErrors = traceableSender.Errors;

                // Update the Errors message
                myErrors += errorMessage;
                myErrors += "\r\n";

                // check for the constraint on this._Errors string length.
                // the purpose of this check is to avoid a possible memory leak.
                if (myErrors.Length > _ErrorsMaxSize)
                {
                    myErrors = errorMessage;
                }

                traceableSender.Errors = myErrors;

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Set the default maximum length of the Errors property to 4096 bytes.
        /// This threshold should prevent memory leak due to exceptions storms.
        /// </summary>
        static private int _ErrorsMaxSize = 4096;
        /// <summary>
        /// Gets or Sets the maximum length in bytes for the 'Errors' property.
        /// Default value is 4096 bytes.
        /// </summary>
        static public int ErrorsMaxSize
        {
            get
            {
                return _ErrorsMaxSize;
            }
            set
            {
                if (value > 0)
                {
                    _ErrorsMaxSize = value;
                }
            }
        }

    }
}
